#include "FeedbackRotation.h"
#include "../Operations/OperationManager.h"

using namespace std;

const float CIView::FeedbackRotation::CIRCLE_RADIUS = 18.0f;

/// <summary>
/// Constructor
/// </summary>
CIView::FeedbackRotation::FeedbackRotation() : x (false), y (false), z (false) {}

/// <summary>
/// Destructor
/// </summary>
CIView::FeedbackRotation::~FeedbackRotation() {}







///<************************************************************************************************************>
///<*********************************** region Protected Utility Methods ***************************************>
///<************************************************************************************************************>

/// <summary>
/// Method called to draw a the x-axis feedback rotation
/// </summary>
void CIView::FeedbackRotation::DrawX_Axis() {
    if (y || z)
        return;
        
    glLineWidth (1.0f);

    glPushMatrix();
        glRotatef (-90, 0, 1, 0); 
        DrawCircle();             
    glPopMatrix();
}

/// <summary>
/// Method called to draw a the y-axis feedback rotation
/// </summary>
void CIView::FeedbackRotation::DrawY_Axis() {
    if (x || z)
        return;
        
    glLineWidth (1.0f);

    glPushMatrix();
        glRotatef (-90, 1, 0, 0); 
        DrawCircle(); 
    glPopMatrix();
}

/// <summary>
/// Method called to draw a the z-axis feedback rotation
/// </summary>
void CIView::FeedbackRotation::DrawZ_Axis() {
    if (x || y)
        return;
        
    glLineWidth (1.0f);

    //z-axis
    DrawCircle(); 
}

/// <summary>
/// Method called to draw a feedback while an axis is selected
/// </summary>
/// <param name="camera"> A reference to the current camera </param>
void CIView::FeedbackRotation::DrawFeedback (Camera& camera) {
    glColor3f (1, 1, 0);
    glLineWidth (1.0f);

    glPushMatrix();
        if (x && y && z) {              //All axes selected                    
            glRotatef (-camera.orientation.y, 0.0f, 1.0f, 0.0f);                //Rotates the scene on y-axis according to the camara orientation    
            glRotatef (-camera.orientation.x, 1.0f, 0.0f, 0.0f);                //Rotates the scene on x-axis according to the camara orientation                
            glRotatef (-camera.orientation.z, 0.0f, 0.0f, 1.0f);                //Rotates the scene on z-axis according to the camara orientation         
        }
        else if (x) {                   //x-axis selected
            glRotatef (90, 0, 1, 0); 
            DrawFeedbackCircle();    //Draws the entire circle around the rotation axis
        }   
        else if (y) {                   //y-axis selected
            glRotatef (-90, 1, 0, 0); 
            DrawFeedbackCircle();    //Draws the entire circle around the rotation axis
        }  
        else if (z) {                   //z-axis selected

            DrawFeedbackCircle();    //Draws the entire circle around the rotation axis     
        }        

        //Drawing the rotation angle boounding lines
        glBegin (GL_LINES);
            glVertex3f (0, 0, 0);
            glVertex3f (0, CIRCLE_RADIUS, 0);
            
            glVertex3f (0, 0, 0);
            glVertex3f (cos (CICore::PI*0.5f + angle)*CIRCLE_RADIUS,
                        sin (CICore::PI*0.5f + angle)*CIRCLE_RADIUS,
                        0);
        glEnd();

        
        //Drawing the rotation angle feedback
        glPushMatrix();            
            //Enabling blending for transparency
            glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable (GL_BLEND);                                //Enables blending

            glColor4f (1, 1, 0, 0.5f);

            float tmp = angle;

            while (tmp < 0)
                tmp += 2 * CICore::PI;

            float b = tmp <= CICore::PI ? 0 : tmp;
            float n = tmp <= CICore::PI ? tmp : 2*CICore::PI;

            glRotatef (90, 0, 0, 1);

            //Drawing the rotation angle feedback
            glBegin (GL_TRIANGLE_FAN);  
                glVertex3f (0, 0, 0);
                for (float radians = b; radians < n; radians += 0.015f) {
                    glVertex3f (cos (radians) * CIRCLE_RADIUS, sin (radians) * CIRCLE_RADIUS, 0);                      
                }
            glEnd();

            glDisable (GL_BLEND);                               //Disables blending
        glPopMatrix();
    glPopMatrix();
}

/// <summary>
/// Method called to draw the feedback circle
/// </summary>
void CIView::FeedbackRotation::DrawFeedbackCircle() {
    float n = 2 * CICore::PI;
    float x, y;
        
    glLineWidth (1.0f);

    glNormal3f (0, 0, 1);

    glBegin (GL_LINE_LOOP);
        for (float angle = 0; angle < n; angle += 0.005f) {
            x = cos(angle) * CIRCLE_RADIUS;
            y = sin(angle) * CIRCLE_RADIUS;
            glVertex3f (x, y, 0);  
        }
    glEnd();
}


/// <summary>
/// Method called to draw a circle representing an axis
/// </summary>
void CIView::FeedbackRotation::DrawCircle() {
    float n = 2 * CICore::PI;
    float x, y, z;

    z = 15.0f / CIRCLE_RADIUS;

    CICore::Vector3D normal;
        
    glLineWidth (1.0f);

    glBegin (GL_LINE_LOOP);
        for (float angle = 0; angle < n; angle += 0.005f) {
            x = cos(angle) * CIRCLE_RADIUS;
            y = sin(angle) * CIRCLE_RADIUS;

            //The normal is the cross product
            normal.x = 2*z*y;  //y*(z) - (-z)*y
            normal.y =  -2*z*x;  //(-z)*x - x*z
            //normal.z = 0;       //x*y - y*x
            normal.Normalize();
            
            glNormal3f (normal.x, normal.y, normal.z);
            glVertex3f (x, y, -z);   
            glVertex3f (x, y,  z);  
        }
    glEnd();
}

/// <summary>
/// Method called to define a temporary clipping plane 
/// (used to cut the needless part of the circles axes)
/// </summary>
/// <param name="camera"> A reference to the current camera </param>
void CIView::FeedbackRotation::DefineClippingPlane (Camera& camera) {
    GLfloat m[16];                                  //Temporary array to store a matrix 4x4
    glGetFloatv(GL_MODELVIEW_MATRIX, m);            //Gets the current model view matrix into the temporary array m

    //Recovering the x-axis (s) and the y-axis (w) vectors of the camera's coordinate system                
    CICore::Vector3D s (m[0], m[4], m[8]);
    CICore::Vector3D w (m[1], m[5], m[9]);

    //Computing the coefficients of the cartesian equation of the plane, 
    //using two vectors (s and w) parallel to the plane and 
    //a point (position) that lies on the same    
    float a = s.y*w.z - w.y*s.z;
    float b = s.z*w.x - w.z*s.x;
    float c = s.x*w.y - w.x*s.y;
    float d = a*(-position.x) + b*(-position.y) + c*(-position.z);

    //The clipping plane cartesian equation used for rotation feedback
    double clipPlaneEq[] = { a, b, c, d }; 

    glEnable (GL_CLIP_PLANE0);
    glClipPlane (GL_CLIP_PLANE0, clipPlaneEq);          //Defines a clipping plane to cut the useless part of the feedback
}
/***************************************************************************************************************
************************************** endregion Protected Utility Methods *************************************
****************************************************************************************************************/













///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary>
/// Method called to compute the feedback position
/// </summary>
/// <param name="scene"> A reference to the 3D scene </param>
void CIView::FeedbackRotation::ComputeFeedbackPosition (Scene& scene) {
    map<const char*, CIGeom::Geometry*>& geometries = scene.GetGeometries();
    map<const char*, CIGeom::Geometry*>::iterator it = geometries.begin();
    renderizable = false;
    
    DeselectAll();  //Deselects all feedback axes
    CIGeom::Box box;        //The bounding box of the selected vertices

    //Checking selected vertices
    while (it != geometries.end()) {
        CICore::VertexLib& vertices = it->second->GetVertices();
        CICore::VertexIter v = vertices.begin();

        if (it->second->hasPrimitivesSelected) {

            while (v != vertices.end()) {
                if ((*v)->selected) {           
                    renderizable = true;        //Since there is a vertex selected, the feedback can be rendered
                    box.Update (*(*v));         //Updates the bounding box of the selected vertices
                }
                v++;
            }
        }

        it++;
    }

    //If the rotation is around the origin
    if (CIOps::OperationManager::Origin)
        position = CICore::Point::ORIGIN;               //The feedback center is the origin
    else
        position = box.GetCentre();                     //Computes the centre of the bounding box
}

/// <summary>
/// Method called to draw the rotation feedback
/// </summary>
/// <param name="camera"> A reference to the current camera </param>
void CIView::FeedbackRotation::Render (Camera& camera) {
    if( !renderizable)      //If the feedback can't be rendered (for example, because there are no primitives selected)
        return;             //must return
    

    GLint oldModel;
    glGetIntegerv (GL_SHADE_MODEL, &oldModel);      //Gets the current shade model
    glShadeModel (GL_SMOOTH);                       //Sets the smooth shade model

    double scaleValue = 1.0 / camera.zoom;
    
    glPushMatrix();        
        glTranslatef (position.x, position.y, position.z);
        glScaled (scaleValue, scaleValue, scaleValue);

        //TODO -> Use materials instead of glColor!
        
        //If at least an axis is selected, must draw a feedback
        if (x || y || z)      
            DrawFeedback (camera);
    glPopMatrix();
        
    glPushMatrix();
        //Defining a temporary clipping plane (used to cut the needless part of the circles axes)
        DefineClippingPlane (camera);

        glTranslatef (position.x, position.y, position.z);
        glScaled (scaleValue, scaleValue, scaleValue);
              
        bool allSelected = x && y && z;

        //z-axis
        glPushMatrix();
            (z && !allSelected) ? glColor3f (1, 1, 0) : glColor3f (0, 0, 1);    //The z-axis color according to its selection status            
            DrawZ_Axis();
        glPopMatrix();    

        //y-axis
        glPushMatrix();
            (y && !allSelected) ? glColor3f (1, 1, 0) : glColor3f (0, 1, 0);    //The y-axis color according to its selection status            
            DrawY_Axis();
        glPopMatrix();

        //x-axis
        glPushMatrix();
            (x && !allSelected) ? glColor3f (1, 1, 0) : glColor3f (1, 0, 0);    //The x-axis color according to its selection status                            
            DrawX_Axis();            
        glPopMatrix();        
        
        glDisable (GL_CLIP_PLANE0);         //Disables the clipping plane
        
        //The bounding circle         
        glPushMatrix();            
            allSelected ? glColor3f (1, 1, 0) : glColor3f (0.5f, 0.5f, 0.5f);   //The bounding circle color according to its selection status
            
            glRotatef (-camera.orientation.y, 0.0f, 1.0f, 0.0f);                //Rotates the scene on y-axis according to the camara orientation    
            glRotatef (-camera.orientation.x, 1.0f, 0.0f, 0.0f);                //Rotates the scene on x-axis according to the camara orientation                
            glRotatef (-camera.orientation.z, 0.0f, 0.0f, 1.0f);                //Rotates the scene on z-axis according to the camara orientation         

            DrawCircle(); 
        glPopMatrix();

    glPopMatrix();  

    glShadeModel (oldModel);                //Restores the old shade model
}

/// <summary>
/// Method called to load the names for the axes in the OpenGL names stack       
/// </summary>
/// <param name="camera"> A reference to the current camera </param>
void CIView::FeedbackRotation::LoadAxesNames (Camera& camera) {
    if( !renderizable)      //If the feedback can't be rendered (for example, because there are no primitives selected)
        return;             //must return

    //Defining a temporary clipping plane (used to cut the needless part of the circles axes)
    DefineClippingPlane (camera);

    double scaleValue = 1.0 / camera.zoom;

    glPushMatrix();                 
        glTranslatef (position.x, position.y, position.z);
        glScaled (scaleValue, scaleValue, scaleValue);

        if (camera.type != Camera::RIGHT) {
            //x-axis
            glLoadName (X_AXIS);                            //Loads the name for the x-axis
            DrawX_Axis();
        }

        if (camera.type != Camera::TOP) {
            //y-axis           
            glLoadName (Y_AXIS);                            //Loads the name for the y-axis
            DrawY_Axis();           
        }

        if (camera.type != Camera::FRONT) {
            //z-axis            
            glLoadName (Z_AXIS);                            //Loads the name for the z-axis
            DrawZ_Axis();            
        }
    glPopMatrix();

    glDisable (GL_CLIP_PLANE0);         //Disables the clipping plane
}

/// <summary>
/// Method called to set the x-axis as selected
/// </summary>
void CIView::FeedbackRotation::SelectX() {
    x = true;
    y = false;
    z = false;
}

/// <summary>
/// Method called to set the y-axis as selected
/// </summary>
void CIView::FeedbackRotation::SelectY() {
    x = false;
    y = true;
    z = false;
}

/// <summary>
/// Method called to set the z-axis as selected
/// </summary>
void CIView::FeedbackRotation::SelectZ() {
    x = false;
    y = false;
    z = true;
}

/// <summary>
/// Method called to set all axes as selected
/// </summary>
void CIView::FeedbackRotation::SelectAll() {
    x = true;
    y = true;
    z = true;
}

/// <summary>
/// Method called to set all axes as deselected
/// </summary>
void CIView::FeedbackRotation::DeselectAll() {
    x = false;
    y = false;
    z = false;
}

/// <summary>
/// Method called to know if the x-axis is selected
/// </summary>
/// <returns> A boolean that indicates if the x-axis is selected </returns>
bool CIView::FeedbackRotation::IsSelectedX() {
    return x;
}

/// <summary>
/// Method called to know if the y-axis is selected
/// </summary>
/// <returns> A boolean that indicates if the y-axis is selected </returns>
bool CIView::FeedbackRotation::IsSelectedY() {
    return y;
}

/// <summary>
/// Method called to know if the z-axis is selected
/// </summary>
/// <returns> A boolean that indicates if the z-axis is selected </returns>
bool CIView::FeedbackRotation::IsSelectedZ() {
    return z;
}

/// <summary>
/// Method called to get the feedback position
/// </summary>
/// <returns> The feedback position </returns>
CICore::Point& CIView::FeedbackRotation::GetPosition() {
    return position;
}

/// <summary>
/// Method called to set the rotation angle, so it can show the feedback while rotating
/// </summary>
/// <param name="radians"> The rotation angle value (expressed in radians) </param>
void CIView::FeedbackRotation::SetRotationAngle (float radians) {
    angle = radians;
}
/***************************************************************************************************************
******************************************* endregion Utility Methods ******************************************
****************************************************************************************************************/

